package joe;

import java.io.File;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.SymbolLookup;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.nio.MappedByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.CRC32;
import jdk.vm.ci.code.site.DataPatch;
import jdk.vm.ci.code.site.Mark;
import jdk.vm.ci.code.site.Site;
import jdk.vm.ci.hotspot.HotSpotCompiledCode;
import jdk.vm.ci.hotspot.HotSpotCompiledNmethod;
import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod;
import jdk.vm.ci.meta.Assumptions;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.runtime.JVMCI;
import jdk.vm.ci.runtime.JVMCICompiler;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.tinylog.Logger;
import jdk.internal.misc.Unsafe;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.JAVA_LONG;

public final class UnsafeUtil {
	private static final @NotNull Unsafe unsafe;
	private static final @NotNull MethodHandle getDeclaredMethods0MH;
	private static final long OVERRIDE_OFFSET;
	private static final boolean isWindows;
	private static final @Nullable MethodHandle mhGetLastError, mhVirtualAlloc, mhVirtualFree;
	private static final ConcurrentHashMap<Long, Long> allocPageMap = new ConcurrentHashMap<>();
	private static final ConcurrentHashMap<Long, Long> allocMemMap = new ConcurrentHashMap<>();
	private static final boolean FORCE_USE_DLL = false;
	private static final @Nullable MethodHandle mhMatVecMulAvx;
	private static final @Nullable MethodHandle mhMatMulAvx;

	static {
		try {
			var theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
			theUnsafeField.setAccessible(true);
			unsafe = (Unsafe)theUnsafeField.get(null);
			for (long i = 8; ; i++) {
				if (unsafe.getBoolean(theUnsafeField, i)) {
					theUnsafeField.setAccessible(false);
					if (!unsafe.getBoolean(theUnsafeField, i)) {
						OVERRIDE_OFFSET = i;
						break;
					}
					theUnsafeField.setAccessible(true);
				}
				if (i == 32) // should be enough
					throw new UnsupportedOperationException(System.getProperty("java.version"));
			}
			var lookup = MethodHandles.lookup();
			getDeclaredMethods0MH = lookup.unreflect(setAccessible(
					Class.class.getDeclaredMethod("getDeclaredMethods0", boolean.class)));

			var m = getMethod(Module.class, "implAddOpensToAllUnnamed", String.class);
			var jvmciModule = Class.forName("jdk.vm.ci.runtime.JVMCI").getModule();
			m.invoke(jvmciModule, "jdk.vm.ci.code");      // --add-opens jdk.internal.vm.ci/jdk.vm.ci.code=ALL-UNNAMED
			m.invoke(jvmciModule, "jdk.vm.ci.code.site"); // --add-opens jdk.internal.vm.ci/jdk.vm.ci.code.site=ALL-UNNAMED
			m.invoke(jvmciModule, "jdk.vm.ci.hotspot");   // --add-opens jdk.internal.vm.ci/jdk.vm.ci.hotspot=ALL-UNNAMED
			m.invoke(jvmciModule, "jdk.vm.ci.runtime");   // --add-opens jdk.internal.vm.ci/jdk.vm.ci.runtime=ALL-UNNAMED
			m.invoke(jvmciModule, "jdk.vm.ci.meta");      // --add-opens jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED

			isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");

			MethodHandle mhGLE = null, mhVA = null, mhVF = null;
			if (isWindows) {
				try {
					var libKernel32 = SymbolLookup.libraryLookup("kernel32.dll", Arena.global());
					var linker = Linker.nativeLinker();
					// DWORD GetLastError();
					mhGLE = linker.downcallHandle(libKernel32.find("GetLastError").orElseThrow(),
							FunctionDescriptor.of(JAVA_INT));
					// LPVOID VirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
					mhVA = linker.downcallHandle(libKernel32.find("VirtualAlloc").orElseThrow(),
							FunctionDescriptor.of(JAVA_LONG, JAVA_LONG, JAVA_LONG, JAVA_INT, JAVA_INT));
					// BOOL VirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType);
					mhVF = linker.downcallHandle(libKernel32.find("VirtualFree").orElseThrow(),
							FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_INT));
				} catch (Exception e) { // logger.warn; ignore
					Logger.warn(e, "UnsafeUtil: load kernel32.dll exception");
					mhVA = null;
				}
			}
			mhGetLastError = mhGLE;
			mhVirtualAlloc = mhVA;
			mhVirtualFree = mhVF;
		} catch (ReflectiveOperationException e) {
			throw new ExceptionInInitializerError(e);
		}

		MethodHandle mhMatVecMulAvx0 = null;
		MethodHandle mhMatMulAvx0 = null;
		var linked = false;
		if (!FORCE_USE_DLL) {
			try {
				// now only for windows and x64 with AVX & FMA3, see vec_x64.asm
				linkCode(UnsafeUtil.class.getMethod("matVecMulAvxNative", long.class, long.class, long.class), new byte[]{
						(byte)0xC4, (byte)0xE2, (byte)0x7D, (byte)0x1A, (byte)0x02, (byte)0xC4, (byte)0xC1, (byte)0x7C,
						(byte)0x10, (byte)0x10, (byte)0xC4, (byte)0xC1, (byte)0x7C, (byte)0x10, (byte)0x58, (byte)0x20,
						(byte)0xC5, (byte)0xFC, (byte)0x15, (byte)0xC8, (byte)0xC5, (byte)0xFC, (byte)0x14, (byte)0xC0,
						(byte)0xC5, (byte)0xFD, (byte)0xC6, (byte)0xC0, (byte)0x0C, (byte)0xC5, (byte)0xF5, (byte)0xC6,
						(byte)0xC9, (byte)0x0C, (byte)0xC5, (byte)0xFC, (byte)0x59, (byte)0xC2, (byte)0xC4, (byte)0xE2,
						(byte)0x75, (byte)0xB8, (byte)0xC3, (byte)0xC4, (byte)0xE3, (byte)0x7D, (byte)0x06, (byte)0xC8,
						(byte)0x01, (byte)0xC5, (byte)0xFC, (byte)0x58, (byte)0xC1, (byte)0xC4, (byte)0xC1, (byte)0x78,
						(byte)0x11, (byte)0x01, (byte)0xC5, (byte)0xF8, (byte)0x77, (byte)0xC3,
						0, 0, // 4-byte align padding
						0x41, (byte)0x81, 0x7f, 0, 0 // barrier
				});
				// now only for windows and x64 with AVX & FMA3, see vec_x64.asm
				linkCode(UnsafeUtil.class.getMethod("matMulAvxNative", long.class, long.class, long.class), new byte[]{
						(byte)0xC5, (byte)0xFC, (byte)0x10, (byte)0x02, (byte)0xC5, (byte)0xFC, (byte)0x10, (byte)0x4A,
						(byte)0x20, (byte)0xC4, (byte)0xC2, (byte)0x7D, (byte)0x1A, (byte)0x20, (byte)0xC4, (byte)0xC3,
						(byte)0x4D, (byte)0x06, (byte)0x30, (byte)0x33, (byte)0xC5, (byte)0xFC, (byte)0xC6, (byte)0xD0,
						(byte)0x00, (byte)0xC5, (byte)0xF4, (byte)0xC6, (byte)0xD9, (byte)0x00, (byte)0xC5, (byte)0xEC,
						(byte)0x59, (byte)0xD4, (byte)0xC5, (byte)0xE4, (byte)0x59, (byte)0xDC, (byte)0xC5, (byte)0xFC,
						(byte)0xC6, (byte)0xE0, (byte)0x55, (byte)0xC5, (byte)0xF4, (byte)0xC6, (byte)0xE9, (byte)0x55,
						(byte)0xC4, (byte)0xE2, (byte)0x5D, (byte)0xB8, (byte)0xD6, (byte)0xC4, (byte)0xE2, (byte)0x55,
						(byte)0xB8, (byte)0xDE, (byte)0xC5, (byte)0xFC, (byte)0xC6, (byte)0xE0, (byte)0xAA, (byte)0xC5,
						(byte)0xF4, (byte)0xC6, (byte)0xE9, (byte)0xAA, (byte)0xC4, (byte)0xC2, (byte)0x7D, (byte)0x1A,
						(byte)0x70, (byte)0x20, (byte)0xC4, (byte)0xE2, (byte)0x6D, (byte)0x98, (byte)0xE6, (byte)0xC4,
						(byte)0xE2, (byte)0x65, (byte)0x98, (byte)0xEE, (byte)0xC5, (byte)0xFC, (byte)0xC6, (byte)0xC0,
						(byte)0xFF, (byte)0xC5, (byte)0xF4, (byte)0xC6, (byte)0xC9, (byte)0xFF, (byte)0xC4, (byte)0xC3,
						(byte)0x4D, (byte)0x06, (byte)0x70, (byte)0x20, (byte)0x33, (byte)0xC4, (byte)0xE2, (byte)0x5D,
						(byte)0x98, (byte)0xC6, (byte)0xC4, (byte)0xE2, (byte)0x55, (byte)0x98, (byte)0xCE, (byte)0xC4,
						(byte)0xC1, (byte)0x7C, (byte)0x11, (byte)0x01, (byte)0xC4, (byte)0xC1, (byte)0x7C, (byte)0x11,
						(byte)0x49, (byte)0x20, (byte)0xC5, (byte)0xF8, (byte)0x77, (byte)0xC3,
						0, 0, // 4-byte align padding
						0x41, (byte)0x81, 0x7f, 0, 0 // barrier
				});
				linked = true;
			} catch (NoSuchMethodException e) {
				throw forceThrow(e);
			} catch (UnsupportedOperationException ignored) { // for GraalVM Native Image
			}
		}
		if (!linked) {
			var libVecX64 = SymbolLookup.libraryLookup("vec_x64.dll", Arena.global());
			var linker = Linker.nativeLinker();
			mhMatVecMulAvx0 = linker.downcallHandle(libVecX64.find("matVecMulAvx").orElseThrow(),
					FunctionDescriptor.ofVoid(JAVA_LONG, JAVA_LONG, JAVA_LONG), Linker.Option.critical(false));
			mhMatMulAvx0 = linker.downcallHandle(libVecX64.find("matMulAvx").orElseThrow(),
					FunctionDescriptor.ofVoid(JAVA_LONG, JAVA_LONG, JAVA_LONG), Linker.Option.critical(false));
		}
		mhMatVecMulAvx = mhMatVecMulAvx0;
		mhMatMulAvx = mhMatMulAvx0;
	}

	private UnsafeUtil() {
	}

	@SuppressWarnings("unchecked")
	@Contract("_ -> fail")
	public static <E extends Throwable> RuntimeException forceThrow(@NotNull Throwable e) throws E {
		throw (E)e;
	}

	public static long allocateMemoryPage(long bytes) {
		var mhVA = mhVirtualAlloc;
		if (mhVA != null) {
			try {
				var ptr = (long)mhVA.invokeExact(0L, bytes, 0x3000, 4); // MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE
				if (ptr != 0) {
					if ((ptr & 0xfff) == 0) { // 4K aligned
						allocPageMap.put(ptr, bytes);
						return ptr;
					}
					Logger.error("UnsafeUtil: allocateMemoryPage invoke VirtualAlloc not aligned: ptr = {}", ptr);
					//noinspection DataFlowIssue
					var r = (int)mhVirtualFree.invokeExact(ptr, 0L, 0x8000); // MEM_RELEASE
					if (r == 0) {
						var mhGLE = mhGetLastError;
						Logger.error("UnsafeUtil: allocateMemoryPage invoke VirtualFree failed: {}",
								mhGLE != null ? (int)mhGLE.invokeExact() : -1);
					}
				} else {
					var mhGLE = mhGetLastError;
					Logger.error("UnsafeUtil: allocateMemoryPage invoke VirtualAlloc failed: {}",
							mhGLE != null ? (int)mhGLE.invokeExact() : -1);
				}
			} catch (Throwable e) { // logger.error
				Logger.error(e, "UnsafeUtil: allocateMemoryPage invoke VirtualAlloc exception");
			}
		}
		return 0;
	}

	public static boolean freeMemoryPage(long ptr) {
		if (ptr == 0)
			return true;
		if (allocPageMap.remove(ptr) == null)
			return false;
		try {
			//noinspection DataFlowIssue
			var r = (int)mhVirtualFree.invokeExact(ptr, 0L, 0x8000); // MEM_RELEASE
			if (r == 0) {
				var mhGLE = mhGetLastError;
				Logger.error("UnsafeUtil: freeMemoryPage invoke VirtualFree failed: {}",
						mhGLE != null ? (int)mhGLE.invokeExact() : -1);
			}
		} catch (Throwable e) { // logger.error
			Logger.error(e, "UnsafeUtil: freeMemoryPage invoke VirtualFree exception");
		}
		return true;
	}

	public static long allocateMemory(long bytes) {
		if (bytes >= 0x10000) {
			var ptr = allocateMemoryPage(bytes);
			if (ptr != 0)
				return ptr;
		}
		var ptr = unsafe.allocateMemory(bytes);
		if (ptr != 0) {
			allocMemMap.put(ptr, bytes);
			return ptr;
		}
		throw new OutOfMemoryError("allocateMemory failed: " + bytes);
	}

	public static boolean freeMemory(long ptr) {
		if (ptr == 0)
			return true;
		if (allocMemMap.remove(ptr) != null) {
			unsafe.freeMemory(ptr);
			return true;
		}
		if (freeMemoryPage(ptr))
			return true;
		Logger.error("UnsafeUtil: freeMemory({}) invalid", ptr);
		return false;
	}

	public static long allocPageCount() {
		return allocPageMap.size();
	}

	public static long allocMemCount() {
		return allocMemMap.size();
	}

	public static long allocPageSize() {
		long r = 0;
		for (var s : allocPageMap.values())
			r += s;
		return r;
	}

	public static long allocMemSize() {
		long r = 0;
		for (var s : allocMemMap.values())
			r += s;
		return r;
	}

	public static void freeMappedByteBuffer(@NotNull MappedByteBuffer mbb) {
		unsafe.invokeCleaner(mbb);
	}

	public static void zeroMemory(long ptr, long size) {
		unsafe.setMemory(ptr, size, (byte)0);
	}

	public static void copyMemory(float @NotNull [] src, long dstPtr) {
		unsafe.copyMemory(src, Unsafe.ARRAY_FLOAT_BASE_OFFSET, null, dstPtr, src.length * 4L);
	}

	public static void copyMemory(byte[] src, long dstPtr) {
		unsafe.copyMemory(src, Unsafe.ARRAY_BYTE_BASE_OFFSET, null, dstPtr, src.length);
	}

	public static void copyMemory(long srcPtr, long dstPtr, long size) {
		unsafe.copyMemory(null, srcPtr, null, dstPtr, size);
	}

	public static byte getByte(long ptr) {
		return unsafe.getByte(ptr);
	}

	public static void putByte(long ptr, byte v) {
		unsafe.putByte(ptr, v);
	}

	public static short getShort(long ptr) {
		return unsafe.getShort(ptr);
	}

	public static int getUShort(long ptr) {
		return unsafe.getShort(ptr) & 0xffff;
	}

	public static void putShort(long ptr, int v) {
		unsafe.putShort(ptr, (short)v);
	}

	public static void putShort(long ptr, int v0, int v1) {
		unsafe.putShort(ptr, (short)v0);
		unsafe.putShort(ptr + 2, (short)v1);
	}

	public static void putShort(long ptr, int v0, int v1, int v2, int v3) {
		unsafe.putShort(ptr, (short)v0);
		unsafe.putShort(ptr + 2, (short)v1);
		unsafe.putShort(ptr + 4, (short)v2);
		unsafe.putShort(ptr + 6, (short)v3);
	}

	public static void putShort(long ptr, int v0, int v1, int v2, int v3, int v4, int v5) {
		unsafe.putShort(ptr, (short)v0);
		unsafe.putShort(ptr + 2, (short)v1);
		unsafe.putShort(ptr + 4, (short)v2);
		unsafe.putShort(ptr + 6, (short)v3);
		unsafe.putShort(ptr + 8, (short)v4);
		unsafe.putShort(ptr + 10, (short)v5);
	}

	public static int getInt(long ptr) {
		return unsafe.getInt(ptr);
	}

	public static int getInt(@NotNull Object obj, long offset) {
		return unsafe.getInt(obj, offset);
	}

	public static void putInt(long ptr, int v) {
		unsafe.putInt(ptr, v);
	}

	public static long getLong(long ptr) {
		return unsafe.getLong(ptr);
	}

	public static void putLong(long ptr, long v) {
		unsafe.putLong(ptr, v);
	}

	public static float getFloat(long ptr) {
		return unsafe.getFloat(ptr);
	}

	public static float getFloat(@NotNull Object obj, long offset) {
		return unsafe.getFloat(obj, offset);
	}

	public static void putFloat(long ptr, float v) {
		unsafe.putFloat(ptr, v);
	}

	public static void putFloat(long ptr, float v0, float v1) {
		var u = unsafe;
		u.putFloat(ptr, v0);
		u.putFloat(ptr + 4, v1);
	}

	public static void putFloat(long ptr, float v0, float v1, float v2) {
		var u = unsafe;
		u.putFloat(ptr, v0);
		u.putFloat(ptr + 4, v1);
		u.putFloat(ptr + 8, v2);
	}

	public static void putFloat(long ptr, float v0, float v1, float v2, float v3) {
		var u = unsafe;
		u.putFloat(ptr, v0);
		u.putFloat(ptr + 4, v1);
		u.putFloat(ptr + 8, v2);
		u.putFloat(ptr + 12, v3);
	}

	public static double getDouble(long ptr) {
		return unsafe.getDouble(ptr);
	}

	public static Object getObj(@NotNull Object obj, long offset) {
		return unsafe.getReference(obj, offset);
	}

	public static long objectFieldOffset(@NotNull Field field) {
		return unsafe.objectFieldOffset(field);
	}

	public static <T extends AccessibleObject> @NotNull T setAccessible(@NotNull T ao) {
		unsafe.putBoolean(ao, OVERRIDE_OFFSET, true);
		return ao;
	}

	public static @NotNull Method @NotNull [] getDeclaredMethods(@NotNull Class<?> klass) {
		try {
			return (Method[])getDeclaredMethods0MH.invokeExact(klass, false);
		} catch (Throwable e) { // rethrow
			throw forceThrow(e);
		}
	}

	public static @NotNull Method getMethod(@NotNull Class<?> klass, @NotNull String methodName,
											@NotNull Class<?>... paramTypes) {
		for (Method method : getDeclaredMethods(klass)) {
			if (method.getName().equals(methodName) && method.getParameterCount() == paramTypes.length) {
				var types = method.getParameterTypes();
				for (int i = 0; ; i++) {
					if (i == paramTypes.length)
						return setAccessible(method);
					if (types[i] != paramTypes[i])
						break;
				}
			}
		}
		throw new IllegalStateException("not found method '" + methodName + "' in " + klass.getName());
	}

	/**
	 * @param libNamePrefix {libNamePrefix}64.dll; lib{libNamePrefix}64.so
	 */
	public static void loadNativeLib(@NotNull ClassLoader classLoader, @Nullable String libPathDefault,
									 @NotNull String libNamePrefix) {
		var nativeLibName = System.mapLibraryName(libNamePrefix + System.getProperty("sun.arch.data.model"));
		var file = libPathDefault != null ? new File(libPathDefault, nativeLibName) : null;
		if (file == null || !file.exists()) {
			var tmpPath = System.getProperty("java.io.tmpdir");
			try {
				var crc = -1L;
				String fileName = null, filePath = null;
				var urls = classLoader.getResources(nativeLibName);
				var urlConn = urls.hasMoreElements() ? urls.nextElement().openConnection() : null;
				if (urlConn instanceof JarURLConnection) {
					var je = ((JarURLConnection)urlConn).getJarEntry();
					if (je != null) {
						crc = je.getCrc() & 0xffff_ffffL;
						fileName = String.format("%08x_%s", crc, nativeLibName);
						filePath = tmpPath + File.separatorChar + fileName;
						file = new File(filePath);
						if (file.length() == je.getSize())
							urlConn = null;
					}
				}
				try (final var is = urlConn != null ? urlConn.getInputStream() : null) {
					if (is != null) {
						var data = is.readAllBytes();
						if (crc < 0) {
							var crc32 = new CRC32();
							crc32.update(data, 0, data.length);
							fileName = String.format("%08x_%s", crc32.getValue(), nativeLibName);
							filePath = tmpPath + File.separatorChar + fileName;
							file = new File(filePath);
							if (file.length() == data.length)
								data = null;
						}
						if (data != null) {
							var tmpFilePath = Files.createTempFile(fileName, ".tmp");
							Files.write(tmpFilePath, data, StandardOpenOption.CREATE,
									StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
							Files.move(tmpFilePath, Paths.get(filePath),
									StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
						}
					}
				}
			} catch (Exception e) { // rethrow
				throw new Error("create library failed: tmpPath=" + tmpPath + ", nativeLibName=" + nativeLibName, e);
			}
		}
		if (file == null)
			file = new File(nativeLibName);
		System.load(file.getAbsolutePath());
	}

	public static void linkCode(@NotNull Method method, byte @NotNull [] code) {
		assert (method.getModifiers() & (Modifier.STATIC | Modifier.NATIVE)) == (Modifier.STATIC | Modifier.NATIVE);
		var jvmci = JVMCI.getRuntime().getHostJVMCIBackend();
		var rm = jvmci.getMetaAccess().lookupJavaMethod(method);
		jvmci.getCodeCache().setDefaultCode(rm, new HotSpotCompiledNmethod(method.getName(), code, code.length,
				new Site[]{new Mark(code.length - 5, 7 /*ENTRY_BARRIER_PATCH*/)}, new Assumptions.Assumption[0],
				new ResolvedJavaMethod[0], new HotSpotCompiledCode.Comment[0], new byte[0], 1, new DataPatch[0],
				true, 0, null, (HotSpotResolvedJavaMethod)rm, JVMCICompiler.INVOCATION_ENTRY_BCI, 1, 0, false));
	}

	public static void matVecMulAvx(long v, long m, long r) {
		if (mhMatVecMulAvx != null) {
			try {
				mhMatVecMulAvx.invokeExact(v, m, r);
			} catch (Throwable e) { // rethrow
				forceThrow(e);
			}
		} else
			matVecMulAvxNative(v, m, r);
	}

	public static void matMulAvx(long m, long M, long r) {
		if (mhMatMulAvx != null) {
			try {
				mhMatMulAvx.invokeExact(m, M, r);
			} catch (Throwable e) { // rethrow
				forceThrow(e);
			}
		} else
			matMulAvxNative(m, M, r);
	}

	// | 0 1 2 3 | 0 1 2 3 |
	//           | 4 5 6 7 |
	//           | 8 9 A B | v(vec4) * m(mat4) => r(vec4)
	//           | C D E F | r[0] = dot(v[0,1,2,3], m[0,4,8,C]); ...
	public static native void matVecMulAvxNative(long v, long m, long r); // need AVX & FMA3

	// | 0 1 2 3 |
	// | 4 5 6 7 |
	// | 8 9 A B | m(mat4) * M(mat4) => r(mat4)
	// | C D E F | r[0] = dot(m[0,1,2,3], M[0,4,8,C]); ...
	public static native void matMulAvxNative(long m, long M, long r); // need AVX & FMA3
}
